/**
 * 按指定键路径对数组中的对象进行分组
 * @param {Array} arr - 包含对象的数组
 * @param {String} keyPath - 用于分组的键路径，如 "category.id"
 * @returns {Array} - 分组后的数组
 * @example
 * groupBy([{id: 1, category: {id: 1, name: "分类1"}}, {id: 2, category: {id: 2, name: "分类2"}}, {id: 3, category: {id: 1, name: "分类1"}}], "category.id")
 * // [
 * //   [
 * //     { id: 1, category: { id: 1, name: '分类1' } },
 * //     { id: 3, category: { id: 1, name: '分类1' } }
 * //   ],
 * //   [
 * //     { id: 2, category: { id: 2, name: '分类2' } }
 * //   ]
 * // ]
 */
function groupBy(arr, keyPath) {
  if (!Array.isArray(arr) || !keyPath) return [];

  // 辅助函数：获取嵌套属性值
  const getValue = (obj, path) => {
    return path.split(".").reduce((acc, part) => acc && acc[part], obj);
  };

  return [
    ...arr
      .reduce((map, item) => {
        const value = getValue(item, keyPath);
        if (value !== undefined) {
          const group = map.get(value) || [];
          group.push(item);
          map.set(value, group);
        }
        return map;
      }, new Map())
      .values(),
  ];
}

/**
 * 按指定键值对数组中的对象进行分组，并提取键值对
 * @param {Array} arr - 包含对象的数组
 * @param {String} key - 用于分组的键名
 * @returns {Array} - 每个元素是一个包含键和对应对象数组的对象
 */
function groupByKey(arr, key) {
  if (!Array.isArray(arr) || !key) return [];

  const groups = arr.reduce((map, item) => {
    const value = item[key];
    if (value !== undefined) {
      const group = map.get(value) || [];
      group.push(item);
      map.set(value, group);
    }
    return map;
  }, new Map());

  return Array.from(groups).map(([keyValue, items]) => ({
    key: key,
    keyValue: keyValue,
    items: items,
  }));
}

// 使用示例
// const data = [
//   { id: 1, category: "A" },
//   { id: 2, category: "B" },
//   { id: 3, category: "A" },
//   { id: 4, category: "C" },
// ];

// const result = groupByKey(data, "category");
// console.log(result);
// 输出:
// [
//   { key: 'category', keyValue: 'A', items: [ { id: 1, category: 'A' }, { id: 3, category: 'A' } ] },
//   { key: 'category', keyValue: 'B', items: [ { id: 2, category: 'B' } ] },
//   { key: 'category', keyValue: 'C', items: [ { id: 4, category: 'C' } ] }
// ]

/**
 * 按指定键值对数组中的对象进行分组，并从每个对象中提取多个键值对
 * @param {Array} arr - 包含对象的数组
 * @param {String} groupKey - 用于分组的键名
 * @param {String|Array} extractKeys - 需要提取的键名或键名数组
 * @returns {Array} - 每个元素是一个包含分组键、键值、对象数组和提取的键值对的对象
 * @example
 * groupByAndExtract({ id: 1, category: 'A', name: 'Apple', price: 5 },
  { id: 2, category: 'B', name: 'Banana', price: 3 },
  { id: 3, category: 'A', name: 'Apricot', price: 4 },
  { id: 4, category: 'C', name: 'Cherry' }, 'category', ['name', 'price'])
 */
function groupByAndExtract(arr, groupKey, extractKeys) {
  if (!Array.isArray(arr) || !groupKey) return [];

  // 确保 extractKeys 是数组
  const keysToExtract = Array.isArray(extractKeys)
    ? extractKeys
    : [extractKeys];

  const groups = arr.reduce((map, item) => {
    const groupValue = item[groupKey];

    if (groupValue !== undefined) {
      const group = map.get(groupValue) || {
        key: groupKey,
        keyValue: groupValue,
        items: [],
        extracted: [],
      };

      group.items.push(item);

      // 提取指定的键值对
      const extractedValues = keysToExtract.reduce((acc, key) => {
        if (item[key] !== undefined) {
          acc[key] = item[key];
        }
        return acc;
      }, {});

      // 只添加有提取值的对象
      if (Object.keys(extractedValues).length > 0) {
        group.extracted.push(extractedValues);
      }

      map.set(groupValue, group);
    }

    return map;
  }, new Map());

  return Array.from(groups.values());
}

/**
 * 查找数组中匹配条件的项，并为每个匹配项添加新数据
 * @param {Array} arr - 包含对象的数组
 * @param {Object} condition - 匹配条件，键值对形式
 * @param {Object} newData - 需要添加的新数据
 * @returns {Array} - 修改后的数组
 */
function findAndAdd(arr, condition, newData) {
  if (!Array.isArray(arr) || !condition || !newData) return arr;

  return arr.map((item) => {
    // 检查当前项是否匹配所有条件
    const isMatch = Object.entries(condition).every(([key, value]) => {
      return item[key] === value;
    });

    // 如果匹配，则合并新数据
    return isMatch ? { ...item, ...newData } : item;
  });
}

/**
 * 在两个数组中基于不同键名找到匹配的对象，并将匹配对象合并到指定键
 * @param {Array} arr1 - 第一个数组
 * @param {Array} arr2 - 第二个数组
 * @param {String} key1 - 第一个数组中用于匹配的键名
 * @param {String} key2 - 第二个数组中用于匹配的键名
 * @param {String} targetKey - 第一个数组中用于存储匹配对象的键名
 * @returns {Array} - 修改后的第一个数组
 */
function matchAndMergeArrays(arr1, arr2, key1, key2, targetKey) {
  // 创建一个 Map 用于存储第二个数组中的对象，以 key2 为键
  const map = new Map();
  arr2.forEach((item) => {
    if (item[key2] !== undefined) {
      map.set(item[key2], item);
    }
  });

  return arr1.map((item) => {
    const matchValue = item[key1];
    if (matchValue !== undefined && map.has(matchValue)) {
      const matchedItem = map.get(matchValue);
      // 确保 targetKey 是一个数组，如果不是则初始化为空数组
      item[targetKey] = item[targetKey] || [];
      item[targetKey].push(matchedItem);
    }
    return item;
  });
}
/**
 * 检查数组是否有重复项
 * @param {Array} arr - 数组
 * @returns {Boolean} - 是否有重复项
 * @example
 * hasDuplicates([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); // false
 * hasDuplicates([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1]); // true
 */
function hasDuplicates(arr) {
  return new Set(arr).size !== arr.length;
}

/**
 * 敏感信息掩码处理
 * @param {string|number} value - 要处理的值
 * @param {Object} [options] - 掩码选项
 * @param {string} [options.type='auto'] - 数据类型：name/mobile/idCard/address/bankCard/money/auto
 * @param {number} [options.prefix=0] - 左侧保留字符数
 * @param {number} [options.suffix=0] - 右侧保留字符数
 * @param {number} [options.replaceLen] - 替换长度（若未指定，则根据类型自动计算）
 * @param {string} [options.maskChar='*'] - 替换字符
 * @param {boolean} [options.keepFormat=false] - 是否保留原始格式（如空格、短横线）
 * @example
 * maskSensitiveInfo('13812345678', { type:'mobile', prefix: 3, suffix: 4 }); // '138****5678'
 * maskSensitiveInfo('张三', { type: 'name', prefix: 1, suffix: 1 }); // '张*'
 * maskSensitiveInfo('123456789012345678', { type: 'bankCard', prefix: 2, suffix: 2 }); // '12***********34'
 * maskSensitiveInfo('1234567890.12', { type:'money', prefix: 3, suffix: 2 }); // '123****0.12'
 * maskSensitiveInfo('1234567890.12', { type:'money', prefix: 3, suffix: 2, replaceLen: 4 }); // '123****0.12'
 * @returns {string} 掩码处理后的字符串
 */
function maskSensitiveInfo(value, options = {}) {
  // 处理非字符串值
  if (value === null || value === undefined) return "";
  if (typeof value !== "string") value = String(value);

  // 合并默认选项
  const defaultOptions = {
    type: "auto", // 自动检测类型
    prefix: 0, // 左侧保留字符数
    suffix: 0, // 右侧保留字符数
    maskChar: "*", // 掩码字符
    keepFormat: false, // 不保留格式
  };
  const opts = { ...defaultOptions, ...options };

  // 清除格式字符（如果不保留格式）
  let cleanValue = value;
  if (!opts.keepFormat) {
    cleanValue = value.replace(/[^\w\s]/gi, "");
  }

  // 根据类型自动设置掩码规则
  if (opts.type === "auto") {
    opts.type = detectType(cleanValue);
  }

  // 应用类型特定的掩码规则
  switch (opts.type) {
    case "name":
      return maskName(cleanValue, opts);
    case "mobile":
      return maskMobile(cleanValue, opts);
    case "idCard":
      return maskIdCard(cleanValue, opts);
    case "address":
      return maskAddress(cleanValue, opts);
    case "bankCard":
      return maskBankCard(cleanValue, opts);
    case "money":
      return maskMoney(cleanValue, opts);
    default:
      return maskCustom(cleanValue, opts);
  }
}

// 自动检测类型
function detectType(value) {
  const len = value.length;

  // 手机号
  if (/^1\d{10}$/.test(value)) return "mobile";

  // 身份证号
  if (/^\d{15}$|^\d{17}[\dxX]$/.test(value)) return "idCard";

  // 银行卡号
  if (len >= 12 && /^\d+$/.test(value)) return "bankCard";

  // 金额（包含小数点）
  if (/^\d+(\.\d{1,2})?$/.test(value)) return "money";

  // 短字符串（可能是姓名）
  if (len <= 4) return "name";

  // 长字符串（可能是地址）
  if (len > 10) return "address";

  return "custom";
}

// 姓名掩码（保留首字符）
function maskName(value, opts) {
  const len = value.length;
  if (len <= 1) return value;
  if (len === 2) return value.charAt(0) + opts.maskChar;

  // 3个及以上字符，保留首尾
  return (
    value.charAt(0) + opts.maskChar.repeat(len - 2) + value.charAt(len - 1)
  );
}

// 手机号掩码（保留前3后4）
function maskMobile(value, opts) {
  return value.replace(
    /^(\d{3})(\d{4})(\d{4})$/,
    `$1${opts.maskChar.repeat(4)}$3`
  );
}

// 身份证号掩码（保留前6后4）
function maskIdCard(value, opts) {
  const len = value.length;
  if (len === 15) {
    return value.replace(
      /^(\d{6})(\d{5})(\d{4})$/,
      `$1${opts.maskChar.repeat(5)}$3`
    );
  }
  return value.replace(
    /^(\d{6})(\d{8})(\d{4})$/,
    `$1${opts.maskChar.repeat(8)}$3`
  );
}

// 地址掩码（保留前6个字符，后面每隔3个显示1个）
function maskAddress(value, opts) {
  const len = value.length;
  if (len <= 6) return value;

  let masked = value.substring(0, 6);
  for (let i = 6; i < len; i++) {
    if ((i - 6) % 4 === 0) {
      masked += value.charAt(i);
    } else {
      masked += opts.maskChar;
    }
  }
  return masked;
}

// 银行卡号掩码（保留前4后4）
function maskBankCard(value, opts) {
  const len = value.length;
  if (len <= 8) return value;

  const prefix = value.substring(0, 4);
  const suffix = value.substring(len - 4);
  const maskedLen = len - 8;

  return `${prefix}${opts.maskChar.repeat(maskedLen)}${suffix}`;
}

// 金额掩码（保留最后2位小数，整数部分每3位显示1位）
function maskMoney(value, opts) {
  const [integer, decimal] = value.split(".");
  let maskedInteger = "";

  for (let i = 0; i < integer.length; i++) {
    if (i % 3 === 0) {
      maskedInteger += integer.charAt(i);
    } else {
      maskedInteger += opts.maskChar;
    }
  }

  return decimal ? `${maskedInteger}.${decimal}` : maskedInteger;
}

// 自定义掩码
function maskCustom(value, opts) {
  const len = value.length;
  const prefix = Math.min(opts.prefix, len);
  const suffix = Math.min(opts.suffix, len - prefix);
  const replaceLen =
    opts.replaceLen !== undefined
      ? Math.min(opts.replaceLen, len - prefix - suffix)
      : len - prefix - suffix;

  return (
    value.substring(0, prefix) +
    opts.maskChar.repeat(replaceLen) +
    value.substring(len - suffix)
  );
}

/**
 * 安全获取对象属性值，支持深度路径
 * @param {Object} obj - 目标对象
 * @param {string|Array<string>} path - 属性路径（如 'a.b.c' 或 ['a', 'b', 'c']）
 * @param {any} [defaultValue=''] - 属性不存在时的默认值
 * @example
 * const user = {
 *   name: "John",
 *   address: {
 *     city: "New York",
 *     details: [{ type: "home", zip: "10001" }],
 *   },
 * };
 *
 * console.log(getSafe(user, "name")); // 'John'
 * console.log(getSafe(user, "age")); // ''（默认值）
 * console.log(getSafe(user, "address.city")); // 'New York'
 * console.log(getSafe(user, "address.street")); // ''
 * console.log(getSafe(user, "address.details[0].zip")); // '10001'
 * console.log(getSafe(user, "address.details[1].zip", "未知")); // '未知'
 * @returns {any} 属性值或默认值
 */
function getSafe(obj, path, defaultValue = "") {
  // 处理空对象或无效路径
  if (obj === null || obj === undefined) return defaultValue;
  if (!path) return obj;

  // 将路径字符串转换为数组
  const pathArray = Array.isArray(path)
    ? path
    : path
        .toString()
        .split(".")
        .filter((segment) => segment !== "");

  // 递归访问路径
  let current = obj;
  for (const segment of pathArray) {
    if (current === null || current === undefined || !(segment in current)) {
      return defaultValue;
    }
    current = current[segment];
  }

  return current === undefined ? defaultValue : current;
}

/**
 * 修改对象的单个键名
 * @param {Object} obj - 要修改的原始对象
 * @param {string} oldKey - 旧键名
 * @param {string} newKey - 新键名
 * @param {Object} [options] - 可选配置项
 * @param {boolean} [options.preserveOriginal=false] - 是否保留原始键
 * @param {boolean} [options.deep=false] - 是否深度遍历对象（递归处理嵌套对象）
 * @param {string[]} [options.excludePaths=[]] - 深度遍历时需要排除的路径（仅在 deep=true 时有效）
 * @param {function} [options.onConflict] - 键名冲突时的处理函数 (newKey, existingValue, newValue) => newValue
 * @example
 * const obj = { a: 1, b: 2, c: { d: 3, e: 4 } };
 * const newObj = renameKey(obj, "a", "x");
 * console.log(newObj); // { x: 1, b: 2, c: { d: 3, e: 4 } }
 * @returns {Object} - 修改后的新对象（不改变原始对象）
 */
function renameKey(obj, oldKey, newKey, options = {}) {
  if (!obj || typeof obj !== "object") {
    throw new Error("Invalid argument: obj must be an object");
  }

  if (
    !oldKey ||
    !newKey ||
    typeof oldKey !== "string" ||
    typeof newKey !== "string"
  ) {
    throw new Error(
      "Invalid key names: oldKey and newKey must be non-empty strings"
    );
  }

  const {
    preserveOriginal = false,
    deep = false,
    excludePaths = [],
    onConflict = (newKey, existingValue, newValue) => newValue,
  } = options;

  // 创建对象的浅拷贝
  const result = { ...obj };

  // 检查是否存在旧键
  if (result.hasOwnProperty(oldKey)) {
    // 检查新键是否已存在
    if (result.hasOwnProperty(newKey)) {
      // 处理键名冲突
      const existingValue = result[newKey];
      const newValue = result[oldKey];
      result[newKey] = onConflict(newKey, existingValue, newValue);
    } else {
      // 正常重命名
      result[newKey] = result[oldKey];
    }

    // 仅在不保留原始键时删除
    if (!preserveOriginal) {
      delete result[oldKey];
    }
  }

  // 深度遍历（递归处理嵌套对象）
  if (deep) {
    Object.keys(result).forEach((key) => {
      const value = result[key];

      // 检查当前路径是否在排除列表中
      const currentPath = key;
      if (excludePaths.includes(currentPath)) {
        return;
      }

      // 递归处理嵌套对象
      if (value && typeof value === "object") {
        result[key] = renameKey(value, oldKey, newKey, {
          ...options,
          // 递归时保留原始配置，但排除路径需要调整
          excludePaths: excludePaths.map((path) =>
            path.startsWith(`${currentPath}.`)
              ? path.slice(currentPath.length + 1)
              : path
          ),
        });
      }
    });
  }

  return result;
}

/**
 * 替换对象的多个键名
 * @param {Object} obj - 目标对象
 * @param {Object} keyMap - 键名映射表 { 旧键名: 新键名 }
 * @param {Object} [options] - 可选配置
 * @param {boolean} [options.preserveOriginal=false] - 是否保留原始键
 * @param {boolean} [options.deep=false] - 是否深度替换嵌套对象
 * @param {function} [options.onConflict] - 键名冲突处理函数 (newKey, existingValue, newValue) => newValue
 * @example
 * // 示例用法
const original = {
  name: "John",
  age: 30,
  userInfo: {
    id: "123",
    address: {
      street: "Main St",
      city: "New York",
    },
  },
  tags: ["a", "b", "c"],
  settings: {
    isActive: true,
    userSettings: {
      preferences: {
        darkMode: true,
      },
    },
  },
};

// 简单批量重命名
const renamed1 = renameKeys(original, {
  name: "fullName",
  age: "years",
  city: "town",
});
console.log(renamed1);
// 输出: { fullName: 'John', years: 30, userInfo: {...}, tags: [...] }

// 深度批量重命名（递归处理嵌套对象）
const renamed2 = renameKeys(
  original,
  {
    name: "fullName",
    city: "town",
    darkMode: "darkTheme",
  },
  { deep: true }
);
console.log(renamed2);
// 输出: { fullName: 'John', age: 30, userInfo: { id: '123', address: { street: 'Main St', town: 'New York' } }, tags: [...], settings: { isActive: true, userSettings: { preferences: { darkTheme: true } } } }

// 保留原始键
const renamed3 = renameKeys(
  original,
  {
    age: "years",
    id: "identifier",
  },
  { preserveOriginal: true }
);
console.log(renamed3);
// 输出: { name: 'John', age: 30, years: 30, userInfo: { id: '123', identifier: '123', address: {...} }, tags: [...] }

// 处理键名冲突
const withConflict = { name: "Alice", newName: "Bob" };
const renamed4 = renameKeys(
  withConflict,
  {
    name: "newName",
  },
  {
    onConflict: (key, existing, newValue) => `${existing} (from ${newValue})`,
  }
);
console.log(renamed4);
// 输出: { newName: 'Bob (from Alice)' }
 * @returns {Object} - 替换后的新对象
 */
function renameKeys(obj, keyMap, options = {}) {
  if (!obj || typeof obj !== "object") {
    throw new Error("Invalid argument: obj must be an object");
  }

  if (!keyMap || typeof keyMap !== "object") {
    throw new Error("Invalid argument: keyMap must be an object");
  }

  const {
    preserveOriginal = false,
    deep = false,
    onConflict = (newKey, existingValue, newValue) => newValue,
  } = options;

  // 创建对象的浅拷贝
  const result = { ...obj };

  // 遍历键名映射表
  Object.entries(keyMap).forEach(([oldKey, newKey]) => {
    // 跳过无效的键名
    if (!oldKey || !newKey || oldKey === newKey) return;

    // 检查是否存在旧键
    if (result.hasOwnProperty(oldKey)) {
      // 处理键名冲突
      if (result.hasOwnProperty(newKey)) {
        const existingValue = result[newKey];
        const newValue = result[oldKey];
        result[newKey] = onConflict(newKey, existingValue, newValue);
      } else {
        // 正常重命名
        result[newKey] = result[oldKey];
      }

      // 仅在不保留原始键时删除
      if (!preserveOriginal) {
        delete result[oldKey];
      }
    }
  });

  // 深度遍历（递归处理嵌套对象）
  if (deep) {
    Object.keys(result).forEach((key) => {
      const value = result[key];

      // 递归处理嵌套对象
      if (value && typeof value === "object" && !Array.isArray(value)) {
        result[key] = renameKeys(value, keyMap, options);
      }

      // 递归处理数组中的对象
      if (Array.isArray(value)) {
        result[key] = value.map((item) =>
          item && typeof item === "object"
            ? renameKeys(item, keyMap, options)
            : item
        );
      }
    });
  }

  return result;
}

/**
 * 删除匹配到特定汉字的字符串
 * @param {string} str - 要处理的字符串
 * @param {string} [matchCondition] - 匹配条件（默认匹配所有汉字）
 * @example
 * const str1 = "你好，世界！";
 * removeMatchedChinese(str1, "你好，世界！"); // "你好，世界！"
 */
function removeMatchedChinese(str, matchCondition) {
  // 默认匹配所有汉字
  const regex = matchCondition
    ? new RegExp(`[${matchCondition}]`, "g")
    : /[\u4e00-\u9fa5]/g;

  return str.replace(regex, "");
}

/**
 * 移除字符串中特定的汉字组合
 * @param {string} str - 待处理的字符串
 * @param {string|string[]} target - 要移除的汉字组合（可以是字符串或数组）
 * @returns {string} - 处理后的字符串
 */
function removeSpecificChinese(str, target) {
  // 将目标转换为数组（支持单个字符串或字符串数组）
  const targets = Array.isArray(target) ? target : [target];

  // 构建正则表达式（转义特殊字符，确保安全）
  const escapedTargets = targets.map((t) =>
    t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
  );

  const regex = new RegExp(escapedTargets.join("|"), "g");

  // 移除匹配的汉字组合
  return str.replace(regex, "");
}
/**
 * 通用分组并查找每组特定字段最小值对应项的函数
 * @param {Array} data - 原始数据数组
 * @param {Function} groupCondition - 分组条件函数，返回分组标识
 * @param {string} targetField - 用于比较最小值的字段名
 * @returns {Object} - 包含每组最小值对应项的对象
 */
function groupByAndFindMin(data, groupCondition, targetField) {
  // 按条件分组数据
  const groups = {};
  data.forEach((item) => {
    const groupKey = groupCondition(item);
    if (!groups[groupKey]) groups[groupKey] = [];
    groups[groupKey].push(item);
  });

  // 查找每组中目标字段的最小值对应项
  const result = {};
  Object.keys(groups).forEach((groupKey) => {
    const items = groups[groupKey];
    if (items.length === 0) {
      result[groupKey] = null;
      return;
    }

    let minItem = items[0];
    items.forEach((item) => {
      if (item[targetField] * 1 < minItem[targetField] * 1) {
        minItem = item;
      }
    });

    result[groupKey] = minItem;
  });

  return result;
}

// 示例用法：根据aaa字段是否等于1分组，查找bb字段最小值
// const data = [
//   { aaa: 1, bb: 10, cc: "A" },
//   { aaa: 2, bb: 5, cc: "B" },
//   { aaa: 1, bb: 8, cc: "C" },
//   { aaa: 3, bb: 3, cc: "D" }
// ];

// const result = groupByAndFindMin(
//   data,
//   item => item.aaa === 1 ? "group1" : "group2",
//   "bb"
// );

// console.log(result);
/* 输出:
{
  group1: { aaa: 1, bb: 8, cc: "C" },
  group2: { aaa: 3, bb: 3, cc: "D" }
}
*/

/**
 * 将数组中每一项的指定字段（是数组）转换为字符串
 * @param {Array} arr - 原数组
 * @param {String} key - 需要处理的字段名
 * @param {String} joinChar - 拼接字符，默认是 ','
 * @returns {Array} - 返回处理后的新数组
 */
function convertArrayFieldToString(arr, key, joinChar = ",") {
  if (!Array.isArray(arr)) return [];

  return arr.map((item) => {
    const newItem = { ...item }; // 避免修改原数据
    if (Array.isArray(newItem[key])) {
      newItem[key] = newItem[key].join(joinChar);
    }
    return newItem;
  });
}

export {
  groupBy,
  groupByKey,
  groupByAndExtract,
  findAndAdd,
  matchAndMergeArrays,
  hasDuplicates,
  maskSensitiveInfo,
  getSafe,
  renameKey,
  renameKeys,
  removeMatchedChinese,
  groupByAndFindMin,
  removeSpecificChinese,
  convertArrayFieldToString,
};
